在這一篇我們要來講一些比較進階的內容。
那就是圖像模糊演算法~
大部分有Debug過IE的人應該都知道,IE是不支援css的filter屬性的。
但是很多時候設計師交上來的稿子裡面又常常會含有很多的模糊特效。
不得不吐槽一下設計師幾乎都只愛那種會吃爆效能的Feature,而且還都屢屢講不聽= =
通常這種時候大部分的工程師都會自己取捨要不要實現這樣的Feature,
本文的主旨就是提供各位一個備選方案~ 也就是用canvas來實作模糊特效。
這篇文一共會分成兩篇來講解,第一篇我們會進行理論面的講解,而第二篇才會進入實作。

不知道大家還對小時候的考試成績結算的方法有沒有印象~
例如小時候老師在月考前會說:『這次月考的成績統計會採加權制度,數學的加權權重會是3, 國/英文是2,社會/自然是1』
老師這樣講的意思就是說~最後平均成績的計算方式會是:
『(數學分數*3+國文分數*2+英文分數*2+社會分數*1+自然分數*1) / (3+2+2+1+1)』
像這樣的分數計算,就是一種加權運算。
而我們在這邊要提到的模糊說穿了其實也就是一種加權運算。
為什麼這樣說呢? 我們先來看看最簡單的圖形模糊演算法: 方框模糊(Box Blur)的做法~

這張照片右半部就是圖像透過方框模糊運算之後產生的結果
熟悉Photoshop操作的人一定知道,模糊其實有分成很多種類,除了我們現在提到的方框模糊(Box Blur)以外,還有動態模糊(Motion Blur), 高斯模糊(Gaussian Blur)...,etc.
這幾種模糊的差別其實就在於背後實現它們的加權運算的權重不太一樣。
以 方框模糊(Box Blur)來講, 它的加權運算是取每顆像素(這邊先把它叫做像素i)的n宮格(九宮格是3*3, 那n宮格就是√n *√n)範圍內的鄰近像素(包括自己),每顆像素的權重都定為1,最後把這些像素加起來取平均(也就是把他們的r/g/b/a值統統個別的加起來,然後不做任何權重加成來取平均), 最後把這個平均賦予給像素i,像這樣的動作去把每個像素都處理一遍,就會變成我們上面看到的圖樣。
如果光看文字還是不懂意思,可以看下面這張圖

在上面這張圖我們可以看到n宮格範圍內的r/g/b值都分別被取了平均,然後賦予到n宮格中間的像素上面。
這個就是方框模糊(Box Blur)的做法。
我們剛剛提到的『n宮格,而且每一格的權重都只有1』,像這樣的一個Pattern,我們先姑且把它稱為像素加權模型,我們可以用一個陣列來表示之:
[
1,1,1
1,1,1
1,1,1
]
像這樣的像素加權模型其實有一個正式的名稱,那就是Convolution kernel(卷積核)。
模糊之所以會有不同的類型,主要原因就是卷積核的不同。
例如以高斯模糊為例,高斯模糊的卷積核,以大小3*3/5*5/7*7的情況來看,大致上個別是這樣:

我們這邊把這樣子的卷積核做成3D模型來看,就看起來會像鐘型分佈

而我們在做高斯模糊的時候,其實就是把一張圖像 的 每個像素 的 卷積核範圍內 的 像素,按照這些像素在卷積核上的權重,來取加權平均,最後把這個加權平均賦予到卷積核中間那顆像素上面。
這個就是圖像模糊運算的經典理論。
我們剛剛提到了圖像模糊運算的經典理論,BUT 實際上我們在前端如果要實現如同理論中描述的運算的話,我們來看看會發生什麼事~
先假設今天我們的卷積核大小是5*5, 然後圖像是一張500*500的png;
for (圖像中的每一顆像素 i ){
let 總和 = 0
let 權重總計 = 0
for (i 的 二十五宮格範圍內所有像素中的某一個顆 j ){
總和 += j* 二十五宮格上面 j位置 的權重
權重總計 += 二十五宮格上面 j位置 的權重
}
let 平均= 總和/權重總計
}
這樣去計算起來,我們做這個運算至少需要跑500*500*5*5 = 6250000 次迴圈才做得完一張圖片的圖像模糊運算。
嚇到了吧~,可以試著想像一下我們親愛的使用者打開你那放了100張圖片的網頁的狀況XD。
上述的這個狀況意味著如果我們照著書上的理論去實作大尺度的模糊濾鏡,肯定是會GG的。
那麼應該怎麼辦呢?
我們需要做的事情簡單來說就是簡化理論,例如最常見的解法就是把n宮格簡化成n十字,也就是先做一次橫向範圍√n格內的的加權平均,然後再做一次縱向範圍√n格內的的加權平均,這麼一來運算的狀況就會變成
for (圖像中的每一顆像素 i ){
let 總和 = 0
let 權重總計 = 0
for (i 的 橫向範圍五格內所有像素中的某一顆 j ){
總和 += j* n宮格上面 j位置 的權重
權重總計 += n宮格上面 j位置 的權重
}
let 平均= 總和/權重總計
// 然後把平均值先賦予給該顆像素
}
for (圖像中的每一顆像素 i ){
let 總和 = 0
let 權重總計 = 0
for (i 的 縱向範圍√n格內所有像素中的某一顆 j ){
總和 += j* n宮格上面 j位置 的權重
權重總計 += n宮格上面 j位置 的權重
}
let 平均= 總和/權重總計
}
用剛剛的情況來看(卷積核大小是5*5, 然後圖像是一張500*500的png), 迴圈次數就變成了2 *500*500*(3+3) = 3000000
直接減少了3250000次~(超過一半)
By This Way 我們就成功削減了一大票需運行迴圈次數~ 而這樣我們就可以在前端實現近似的效果,但是實際與理論細節不同的方框模糊。
我們今天的介紹大概就先到這邊,相信各位已經對模糊的理論與簡化理論的辦法有基本的認識~
明天我們將會繼續來到實作的部分~ 敬請各位期待 :D
P.S 本文後半段在2021/10/11號晚上有調整過,主要是修正效能改良計算的部分,有因應後來實作的方法調整過,如果造成各位不便,還請見諒 >< !
看了開頭就想:咦?那不能請設計師先用PS做完模糊,直接給圖嗎XDD(然後本篇完)
中間對高斯模糊的說法千真萬確,用c++做5x5的就直接幀數爆炸了,還不如直接拿openCV函式庫來用就好,要做動態處理就要想辦法變簡單,除非只是弄弄圖片。
最後一個簡化的想法很好欸ww,猜測是做完水平的平均後,再做垂直的平均,之所以說跟理論細節不同,是因為做水平軸的時候是n十字,已經動過數值後,才做垂直軸就會變成n方框的概念了。
其實那不是我想出來的啦哈哈哈
都只是自己爬文或找原文書來看才知道可以這樣做~
其實模糊演算很有趣的~
有興趣的話還可以讀一下stackblur.js這個插件的演算方法
https://observablehq.com/@jobleonard/mario-klingemans-stackblur